/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit.command;

import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getFragments;
import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getOperands;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.uml2.uml.CombinedFragment;
import org.eclipse.uml2.uml.ExecutionOccurrenceSpecification;
import org.eclipse.uml2.uml.ExecutionSpecification;
import org.eclipse.uml2.uml.InteractionFragment;
import org.eclipse.uml2.uml.InteractionOperand;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.MessageOccurrenceSpecification;
import org.eclipse.uml2.uml.NamedElement;

import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.DiagramElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * Performs geometrical analysis of a diagram in order to detect fragments that are within
 * the borders of a given CombinedFragment (and not within the borders of any other
 * CombinedFragment).
 * <p>
 * The following attributes must be set before the command can run successfully:
 * </p>
 * <ul>
 * <li>CombinedFragmentView
 * <li>EditPart - any EditPart
 * </ul>
 * <p>
 * When completed, the following attributes can be read to access the result:
 * </p>
 * <ul>
 * <li>Contained
 * </ul>
 * 
 * @author frankd@ifi.uio.no
 */
public class AnalyseContainedFragmentsCommand extends Command {

    private GraphNode cfragmentView;
    private EditPart anyEditPart;
    private Map<InteractionOperand, Set<InteractionFragment>> contained;

    public void setAnyEditPart(final EditPart editPart) {
        this.anyEditPart = editPart;
    }

    public void setCombinedFragmentView(final GraphNode view) {
        this.cfragmentView = view;
    }

    public Map<InteractionOperand, Set<InteractionFragment>> getContained() {
        return contained;
    }

    @Override
    public void execute() {
        contained = new HashMap<InteractionOperand, Set<InteractionFragment>>();

        CombinedFragment cfragment = ModelUtil.<CombinedFragment> getSemantic(cfragmentView);
        List<InteractionFragment> candidateFragments =
                (cfragment.getEnclosingOperand() != null) ? getFragments(cfragment
                        .getEnclosingOperand()) : getFragments(cfragment.getEnclosingInteraction());

        for (InteractionFragment fragment : candidateFragments) {
            if (fragment == cfragment)
                continue;

            final Point locationAbs = getLocationAbs(fragment);
            if (locationAbs != null) {
                InteractionFragment owner = getOwner(locationAbs);
                if (getOperands(cfragment).contains(owner))
                    put((InteractionOperand) owner, fragment);
            }
        }
    }

    /**
     * Returns the location of the given fragment, measured in absolute coordinates. TODO
     * Refactor - this is a useful utility method also usable in CoverableAnalyser
     */
    @SuppressWarnings("unchecked")
    private Point getLocationAbs(InteractionFragment fragment) {
        NamedElement lookup;
        if (fragment instanceof MessageOccurrenceSpecification) {
            MessageOccurrenceSpecification eo = (MessageOccurrenceSpecification) fragment;
            lookup = eo.getMessage();
        } else if (fragment instanceof ExecutionOccurrenceSpecification) {
            ExecutionOccurrenceSpecification eo = (ExecutionOccurrenceSpecification) fragment;
            lookup = eo.getExecution();
        } else
            lookup = fragment;
        if (lookup == null)
            return null;

        Diagram diagram = LookForElement.getDiagram(cfragmentView);
        DiagramElement view = ModelUtil.getGraphElement(lookup, diagram);

        if (view instanceof GraphNode) {
            Rectangle viewBounds =
                    new Rectangle(((GraphNode) view).getPosition(), ((GraphNode) view).getSize());
            Point location = viewBounds.getLocation();

            // In the case of ExecutionSpecifications and the event is the
            // "finish" event,
            // we are interested in the bottom end of the figure.
            if (lookup instanceof ExecutionSpecification) {
                location.translate(viewBounds.width / 2, 0);
                if (((ExecutionSpecification) lookup).getFinish() == fragment)
                    location.translate(0, viewBounds.height);
            }

            Map<Object, GraphicalEditPart> editPartRegistry = getEditPartRegistry();
            IFigure figure = editPartRegistry.get(view).getFigure();
            figure.translateToAbsolute(location);

            return location;
        }

        else if (view instanceof GraphEdge) {
            GraphEdge link = (GraphEdge) view;
            Point p;
            if (ModelUtil.<Message> getSemantic(link).getSendEvent() == fragment) {
                p = link.getAnchor().get(0).getPosition();
            } else {
                p = link.getAnchor().get(1).getPosition();
            }
            return p;
            // return part.getConnectionAnchor(anchorRef).getLocation(null);
        } else
            return null;
    }

    @SuppressWarnings("unchecked")
    private Map<Object, GraphicalEditPart> getEditPartRegistry() {
        return (Map<Object, GraphicalEditPart>) anyEditPart.getViewer().getEditPartRegistry();
    }

    /**
     * Returns the nearest enclosing interaction or interaction operand to the specified
     * point.
     * 
     * @param location
     *            in absolute coordinates
     */
    private InteractionFragment getOwner(Point locationAbs) {
        AnalyseOwnerCommand ownerCmd = new AnalyseOwnerCommand();
        ownerCmd.setDiagram(LookForElement.getDiagram(cfragmentView));
        ownerCmd.setAnyEditPart(anyEditPart);
        ownerCmd.setLocation(locationAbs);
        ownerCmd.execute();

        InteractionFragment owner = ownerCmd.getOwner();

        ownerCmd.dispose();
        return owner;
    }

    private void put(InteractionOperand operand, InteractionFragment fragment) {
        Set<InteractionFragment> s = contained.get(operand);
        if (s == null) {
            s = new HashSet<InteractionFragment>();
            contained.put(operand, s);
        }
        s.add(fragment);

    }

    @Override
    public void undo() {
        contained = null;
    }

    @Override
    public void redo() {
        execute();
    }

    public void dispose() {
        cfragmentView = null;
        contained = null;
        anyEditPart = null;
    }
}
